/*
TRABALHO DE PROGRAMACAO CONCORRENTE 2013
FEITO POR:
Danilo Augusto Silva, 6418678
Felipe Fukakusa, 6426976
Jader Bruno Pereira Lima, 6792346
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <mpi.h>


#define MAX_TAMANHO_PALAVRA 10000
#define MAX_TAMANHO_SENTENCA 1000000
#define TRUE 1
#define FALSE 0

typedef struct {
    	int inicio;
    	int fim;
	int totalPalavras;
	int totalPalavrasPalindromos;
	int totalPalavrasPrimos;
	int totalSentencas;
	int totalSentencasPalindromos;
	int totalSentencasPrimos;

}TextoDividido;

//VERIFICA SE O NUMERO N EH PRIMO. EM CASO POSITIVO, INCREMENTA A VARIAVEL TOTALPRIMOS (PASSADA POR REFERENCIA)
int verificaPrimo(int *n, int *totalPrimos){
	//VARIAVEIS
	int i, j;	
	int aux_raiz;
	int aux_boolean = TRUE;
	 
	int vetorCrivo[*n];

	aux_raiz = sqrt( *n );

	//PREENCHIMENTO DO VETOR DE PRIMOS
	for(i = 2; i <= *n; i++) {
		vetorCrivo[i] = i;
	}

	
	for(i = 2; i <= aux_raiz; i++) {
	
		//VETOR EH ZERADO NAS POSICOES NAO PRIMOS
		for( j = 2*i ; j <= *n ; j=j+i )
			vetorCrivo[j] = 0; 

	}

	//ALGORITMO DE CHECAGEM SE N EH PRIMO
	for(i = 2; i <= aux_raiz && aux_boolean; i++){
		if ( vetorCrivo[i] ){
			if((*n % vetorCrivo[i]) == 0){
				aux_boolean = FALSE;
			}	
		}
	}
	
	if (aux_boolean){
		*totalPrimos += 1;
	}
}

//RETIRA OS CARACTERES QUE NAO SAO LETRAS E NUMEROS DA VARIAVEL TEXTO PASSADA POR REFERENCIA
void limpaTexto(char *texto){
	
	//VARIAVEIS
	//INICIALIZACAO DO VETOR DE CARACTERES INVALIDOS (A SER RETIRADOS DOS TEXTOS)
	char invalidos[] = ". , ; : / ? ! [ ] { } ( ) < > # $ % ^ * ' | ~ + - _ @";
	invalidos[ strlen( invalidos ) ] = '"';

	char aux_texto[ strlen( texto ) ];
	int i, j, k = 0;
	int aux_boolean = FALSE;

	for(i = 0; i < strlen(texto); i++) {
		//PARA CADA CHAR, VERIFICA SE NAO ESTA PRESENTE NO VETOR DE CHARS INVALIDOS
		for(j = 0; j < strlen( invalidos ) && !aux_boolean; j++) {
			if(texto[i] == invalidos[j])
				aux_boolean = TRUE;
			if(texto[i] == '\t')
				aux_boolean = TRUE;
		}

		if(!aux_boolean)
			aux_texto[k++] = texto[i];
		else
			aux_boolean = FALSE;
	}	
	aux_texto[k] = '\0';
	//COPIA O O TEXTO LIMPO PARA A VARIAVEL TEXTO INICIAL
	strcpy(texto, aux_texto);
}

//VERIFICA, PARA CADA PALAVRA DA SENTENCA PASSADA POR PARAMETRO, SE E UM PALINDROMO. EM CASO POSITIVO VERIFICA SE A SOMA DOS CODIGOS ASCCI DE SUAS LETRAS EH NUMERO PRIMO. VERIFICA PARA A SENTENCA TAMBEM
void verificaPalindromoNaPalavraESentenca(char *texto, int *totalPalavras, int *totalPalindromosPalavra, int *totalPrimosPalavra, int *totalSentencas, int *totalPalindromosSentenca, int *totalPrimosSentenca ){
	//VARIAVEIS	
	int n, k;
	int totalAscii = 0;
	int aux_boolean;
	int i = 0 ,j = 0, l;
	char palavra[MAX_TAMANHO_PALAVRA];
	char sentenca[MAX_TAMANHO_SENTENCA];

	
	//VERIFICA OS PALINDROMOS NA SENTENCA
	strcpy( sentenca, texto );

	
	while( sentenca[i] != '\0' ) {
		if( sentenca[i] != ' ' )
			//COLOCA TODAS AS SENTENCAS EM MINUSCULA
			if( isupper( sentenca[i] ) )
				sentenca[i] = tolower( sentenca[i] );
		i++;
	}



	//printf("SENTENCA:%s\n", sentenca);
	limpaTexto( sentenca );
	//printf("SENTENCA:%s\n", sentenca);
	
	if (strlen(sentenca) > 2 ){
		aux_boolean = TRUE;
		l = 0;
		n = strlen( sentenca );
		k = n/2;
		
		//TOTAL DO MAPEAMENTO INICIA COM O MAPEAMENTO DA LETRA QUE ESTA NO MEIO
		if(n%2 == 1)				
			totalAscii = sentenca[k];
		else
			totalAscii = 0;

		
		while(aux_boolean && l < k) {
			if( sentenca[l] == sentenca[n - l - 1] )
				totalAscii += ( 2 * sentenca[l++] );
			else 
				aux_boolean = FALSE;
		}
	
		if(aux_boolean) { 
			//printf("PALINDROMO: %s\n", sentenca);
			*totalPalindromosSentenca += 1;
			verificaPrimo( &totalAscii, totalPrimosSentenca );
		}
		*totalSentencas += 1;
	}
	
	
	//VERIFICA PALINDROMOS EM PALAVRAS	

	i = 0;
	aux_boolean = TRUE;

	
	//REPETIR PARA TODAS AS LETRAS DA SENTENCA
	while( i < ( strlen( texto )) + 1 ) {
		//RECUPERA AS PALAVRAS DA SENTENCA (SEPARADAS POR VIRGULA OU ESPACO OU PONTO)
		if( texto[i] != ',' && texto[i] != '\0' && texto[i] != ' ' && texto[i] != '\n') {
			//COLOCA TODAS AS LETRAS EM MINUSCULA
			if( isupper( texto[i] ) )
				texto[i] = tolower( texto[i] );

			palavra[j++] = texto[i];
			palavra[j] = '\0';
		} else {
			j = 0;

			//printf("PALAVRA: %s\n", palavra);
			
			limpaTexto(palavra);
			//printf("PALAVRA: %s\n", palavra);

			//DEFINIMOS QUE UM PALINDROMO DEVE TER 2 LETRAS OU MAIS
			if( strlen( palavra ) > 2 ) {
				//printf("PALAVRA > 2: %s\n", palavra);				
				//printf("STRLEN: %d\n", strlen(palavra));				
				aux_boolean = TRUE;
				l = 0;
				n = strlen( palavra );
				k = n/2;

				//TOTAL DO MAPEAMENTO INICIA COM O MAPEAMENTO DA LETRA QUE ESTA NO MEIO
				if(n%2 == 1)				
					totalAscii = palavra[k];
				else
					totalAscii = 0;
				//MAPEAMENTO DO CODIGO ASCII PARA CADA LETRA
				while( aux_boolean && l < k ) {
					if( palavra[l] == palavra[n - l - 1] ) 
						totalAscii += ( 2 * palavra[l++] );
					else
						aux_boolean = FALSE;	
				}
				
				if(aux_boolean) {
					//printf("PALINDROMO: %s\n", palavra);
					//printf("%s - %d\n", palavra, totalAscii);					
					*totalPalindromosPalavra += 1;
					verificaPrimo( &totalAscii, totalPrimosPalavra );
				} 
			}
		}
		*totalPalavras += 1;
		i++;
	}
}




//PARTICIONA O ARQUIVO DE ENTRADA EM BLOCOS DE tamanho_texto/(num_processos - 1). CADA PROCESSO FICA RESPONSAVEL POR UMA PARTE DO TEXTO. CADA PROCESSO RECEBE AS POSICOES DE INICIO E FIM, QUE DELIMITAM A AREA DE LEITURA DO ARQUIVO DE TEXTO. A FUNCAO TRATA O CORTE DE PALAVRAS, SE CASO UMA PALAVRA ESTEJA NO MEIO DA DIVISAO, A POSICAO DE FIM EH DESLOCADA ATE UMA POSICAO \N. 
void dividirTexto(FILE *file, int *numProcessos, TextoDividido *processo){

	int meioSentenca;
	int tamanhoArquivo, tamanhoTexto;
	int aux_Inicio = 0, aux_Fim = 0;
	int i;
	char c;

	//POSICIONA O PONTEIRO NO FINAL DO ARQUIVO E GUARDA O TAMANHO (POSICAO ATUAL)	
	fseek(file, 0, SEEK_END);
	tamanhoArquivo = ftell(file);
	//POSICIONA O PONTEIRO NOVAMENTE NO COMECO DO ARQUIVO
	rewind(file);

	tamanhoTexto = (int) tamanhoArquivo/(*numProcessos - 1);

	for( i = 0; i < *numProcessos - 1; i++){
		//PARTICIONAMENTO DOS BLOCOS (EXCETO O ULTIMO)		
		if ( i != (*numProcessos - 2) ){
			meioSentenca = 1;				
						
			//POSICIONA O PONTEIRO NA POSICAO FINAL DO TEXTO DIVIDIDO			
			fseek(file, aux_Inicio + tamanhoTexto, SEEK_SET);

			//TRATAMENTO DE CORTE DE SENTENCAS
			while(meioSentenca && !feof(file)){
				fscanf(file, "%c", &c);
				if(c == '\n'){
					aux_Fim = ftell(file);

					processo[i].inicio = aux_Inicio;
					processo[i].fim = aux_Fim;
					processo[i].totalPalavras = 0;
					processo[i].totalPalavrasPalindromos = 0;
					processo[i].totalPalavrasPrimos = 0;
					processo[i].totalSentencas = 0;
					processo[i].totalSentencasPalindromos = 0;
					processo[i].totalSentencasPrimos = 0;

					meioSentenca = 0;
				}

			}

			aux_Inicio = aux_Fim;
		
		}
		//PARTICIONAMENTO DO ULTIMO BLOCO		
		else{
			aux_Fim = ftell( file );    

			processo[i].inicio = aux_Fim;
			processo[i].fim = tamanhoArquivo;
			processo[i].totalPalavras = 0;
			processo[i].totalPalavrasPalindromos = 0;
			processo[i].totalPalavrasPrimos = 0;
			processo[i].totalSentencas = 0;
			processo[i].totalSentencasPalindromos = 0;
			processo[i].totalSentencasPrimos = 0;
		}
	}
}


int main(int argc, char* argv[]){
	//VARIAVEIS	
	//TEXTO A SER LIDO ATE O \N
	char texto[MAX_TAMANHO_SENTENCA];
	FILE *file;
	struct timeval startTime;
	struct timeval endTime;

	//int totalPalavras = 0, totalPalavrasPalindromos = 0, totalPalavrasPrimos = 0;
	//int totalSentencas = 0, totalSentencasPalindromos = 0, totalSentencasPrimos = 0;

	//VARIAVEL DE STATUS DO MPI
	MPI_Status mpi_status;

	//VARIAVEIS MPI
	int numProcessos, numRank, nomeProcessador;
	int processoROOT = 0, processoEscravo;
	
	int i, terminarProcesso = 0, iTexto = 0;	
	
	char c;

	//INICIA O MPI	
	MPI_Init (&argc, &argv);

	//INICIA VARIAVEIS MPI
	MPI_Comm_rank ( MPI_COMM_WORLD, &numRank );	
	MPI_Comm_size ( MPI_COMM_WORLD, &numProcessos );
	printf("EXECUTANDO PROGRAMA PALINDROMO MPI (TEXTO MENOR) (Processo %d)...\n", numRank);
		

	//INICIA CONTAGEM DO TEMPO
	gettimeofday( &startTime, NULL );
	double start = startTime.tv_sec + ( startTime.tv_usec / 1000000.0 );

    	TextoDividido *processo = (TextoDividido*) calloc( numProcessos, sizeof(TextoDividido));

	if( (file = fopen ("shakespe.txt", "r")) != NULL ) {

		if ( numRank == processoROOT ) {
			dividirTexto( file, &numProcessos, processo );
			rewind( file );
			i = 0;

			for (processoEscravo = 1; processoEscravo < numProcessos; processoEscravo++){
				MPI_Send ( &processo[i], sizeof( TextoDividido ), MPI_BYTE, processoEscravo, 0, MPI_COMM_WORLD ); 
				i++;
			}
			//TODOS OS ESCRAVOS DEVEM TERMINAR A TAREFA			
			MPI_Reduce( &numRank, &terminarProcesso, 1, MPI_INT, MPI_SUM, processoROOT, MPI_COMM_WORLD );\
		
		} else {
			
			MPI_Recv ( &processo[numRank - 1], sizeof( TextoDividido ), MPI_BYTE, processoROOT, 0, MPI_COMM_WORLD, &mpi_status ); 
			
			//POSICIONA O PONTEIRO DE LEITURA DO ARQUIVO PARA O INICIO DE CADA TEXTO DIVIDIDO		
			fseek( file, processo[numRank - 1].inicio, SEEK_SET);
		
			//PARA TODO O TEXTO DIVIDIDO
			while(  ftell( file ) < processo[numRank - 1].fim ) {
				fscanf(file, "%c", &c);
				while(c != '\n' && !feof(file)){
					texto[iTexto++] = c;					
					fscanf(file, "%c", &c);
				}
				texto[iTexto] = '\0';
				iTexto = 0;
								
				verificaPalindromoNaPalavraESentenca( 	texto,
									&processo[numRank - 1].totalPalavras, 
									&processo[numRank - 1].totalPalavrasPalindromos,
									&processo[numRank - 1].totalPalavrasPrimos,
									&processo[numRank - 1].totalSentencas,
									&processo[numRank - 1].totalSentencasPalindromos,
									&processo[numRank - 1].totalSentencasPrimos 	);
				
			}
;
			printf( "Escravo: %d - Palavras: %d - Palavras Palindromos: %d - Palavras Primos: %d\n", 
					numRank, 
					processo[numRank - 1].totalPalavras,
					processo[numRank - 1].totalPalavrasPalindromos, 
					processo[numRank - 1].totalPalavrasPrimos 	);

			printf( "Escravo:%d - Sentencas: %d - Sentencas Palindromos: %d - Sentencas Primos: %d\n", 
					numRank, 
					processo[numRank - 1].totalSentencas,
					processo[numRank - 1].totalSentencasPalindromos, 
					processo[numRank - 1].totalSentencasPrimos );

			//GUARDAR VALORES TOTAIS PROCESSADOS PELAS THREADS
			//totalPalavras += processo[i].totalPalavras;
			//totalPalavrasPalindromos += processo[i].totalPalavrasPalindromos;
			//totalPalavrasPrimos += processo[i].totalPalavrasPrimos;
			//totalSentencas += processo[i].totalSentencas;
			//totalSentencasPalindromos += processo[i].totalSentencasPalindromos;
			//totalSentencasPrimos += processo[i].totalSentencasPrimos; 

			MPI_Reduce( &numRank, &terminarProcesso, 1, MPI_INT, MPI_SUM, processoROOT, MPI_COMM_WORLD );
		}

		fclose ( file );		

	} else {

		printf("ERRO: na abertura do arquivo\n");
		MPI_Finalize();
		exit( 1 );
	}

	
	gettimeofday( &endTime, NULL );
	double end = endTime.tv_sec + ( endTime.tv_usec / 1000000.0 );

	printf( "Tempo de execução do Escravo %d: %.3lf segundos\n", numRank, end - start );

	MPI_Finalize();

	
	return EXIT_SUCCESS;

}



